#region Imported Namespaces
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Configuration;
using System.Data;
using System.Web;
using System.Web.UI;
using System.Web.UI.Design;
using System.Web.UI.WebControls;

using Policy = PHSRAG.Insight21.Policy;
using Utility = PHSRAG.Utility;
using Security = PHSRAG.Security;
using UserContext = PHSRAG.Insight21.Context.UserContext;
using Enums = PHSRAG.Security.Enumerations;
using PHSRAG.WebControls;
#endregion

namespace PHSRAG.Insight21.InsightWebControls
{
	#region Class OrganizationSelectors
	/// <summary>
	/// OrganizationSelectors is a control that displays four ASP.NET dropdown list controls.
	/// These controls are dynamically generated based on the logged in users organization
	/// access starting at the Domain, Institution, Department and Unit level. Therefore,
	/// changing any of the higher Organization dropdown value (eg. Institution) will
	/// reload all the lower organization dropdowns (Departments and Units) with the departments
	/// in that institution that the user has access to.
	/// </summary>
	[ToolboxData("<{0}:OrganizationSelectors runat=server></{0}:OrganizationSelectors>")]
	[Designer(typeof(OrganizationSelectorsDesigner), typeof(IDesigner))]
	public class OrganizationSelectors : System.Web.UI.WebControls.WebControl, INamingContainer
	{
		#region Instance Variables
		private Policy.ICachePolicy		cachePolicy;
		private	ExtendedDropDownList	domains;
		private	ExtendedDropDownList	institutions;
		private	ExtendedDropDownList	departments;
		private	ExtendedDropDownList	units;
        private bool                    isGrayOutMode;
		#endregion

		#region Constants
		private const string	GetKeyGiverOrganizationListSprocName	= "GetKeyGiverOrganizationList";	
		private const string	GetOrganizationListSprocName			= "GetOrganizationList";
		private const string	DomainDropDownID						= "domains";
		private const string	InstitutionDropDownID					= "institutions";
		private const string	DepartmentDropDownID					= "departments";
		private const string	UnitDropDownID							= "units";
		private const int		RootOrgID								= 1;
		private const string	OrganizationsKey						= "Organizations_6CB405C1-A4BE-4cad-87BF-80F1ABF5A2A0";
		#endregion

		#region Properties
		/// <summary>
		/// Enables or Disables the dropdownlists that make up the organizationSelectors.  If the value 
		/// is true, only the Institutions dropdownlist gets enabled because this is the default
		/// behavior before external disabling/enabling occurs (i.e. when the control is "enabled")
		/// </summary>
		public override bool Enabled
		{
			set
			{
				EnsureChildControls();
				if (value)
					institutions.Enabled = true;

				else
				{
					Reset();
					domains.Enabled = institutions.Enabled = units.Enabled = departments.Enabled = false;
				}
			}
		}

		/// <summary>
		/// Gets the value of the lowest selected Dropdown list. Also sets the
		/// dropdown values by loading the dropdowns based on the type associated
		/// with the organization ID to be set.
		/// </summary>
		public int SelectedOrganizationID
		{
			get 
			{ 
				EnsureChildControls();
				if (units.Items.Count > 0 && units.SelectedValue != Helper.SelectPrompt)
					return Convert.ToInt32(units.SelectedValue);
				if (departments.Items.Count > 0 && departments.SelectedValue != Helper.SelectPrompt)
					return Convert.ToInt32(departments.SelectedValue);
				if (institutions.Items.Count > 0 && institutions.SelectedValue != Helper.SelectPrompt)
					return Convert.ToInt32(institutions.SelectedValue);
				if (domains.Items.Count > 0 && domains.SelectedValue != Helper.SelectPrompt)
					return Convert.ToInt32(domains.SelectedValue);
				return 0;
			}
			set 
			{
				EnsureChildControls();
				DataTable organizationDataTable = OrganizationDataTable;				
				/* The Domains dropdownlist needs to be loaded every time */
				LoadDropDownList(organizationDataTable, domains, (int)OrganizationType.Domain, RootOrgID);
				switch ((int)organizationDataTable.Select(string.Format("Id = {0}", value))[0]["TypeId"])
				{
					/* Clear all dropdownlists below the selected one, set the selected value to the 
					 * user-selected one; load up the dropdownlist below this one in the hierarchy; 
					 * We also need to load up the dropdownlists ABOVE this one in the hierarchy and set the
					 * selected item to handle the case where we are re-arriving at a page, and we 
					 * haven't already loaded the parent dropdownlists.  If we only know a selected 
					 * item of a particular dropdownlist, we need to work our way backwards determining
					 * the parent item of each list, and select it */
					case (int)OrganizationType.Domain:
						domains.SelectedValue = value.ToString();
						institutions.Items.Clear();
						departments.Items.Clear();
						units.Items.Clear();
						LoadDropDownList(organizationDataTable, institutions, (int)OrganizationType.Institution, RootOrgID);
						break;

					case (int)OrganizationType.Institution:
						departments.Items.Clear();
						units.Items.Clear();
						LoadDropDownList(organizationDataTable, institutions, (int)OrganizationType.Institution, RootOrgID);
						institutions.SelectedValue = value.ToString();
						LoadDropDownList(organizationDataTable, departments, (int)OrganizationType.Department, value); 		
						break;

					case (int)OrganizationType.Department:	
						/* Load, Determine and select the parent institution of the selected department */
						LoadDropDownList(organizationDataTable, institutions, (int)OrganizationType.Institution, RootOrgID);
						int deptParentOrgID = (int)organizationDataTable.Select(string.Format("Id = {0}", value))[0]["ParentId"];
						institutions.SelectedValue = deptParentOrgID.ToString();	
					
						LoadDropDownList(organizationDataTable, departments, (int)OrganizationType.Department, deptParentOrgID);
						departments.SelectedValue = value.ToString();
						units.Items.Clear();
						LoadDropDownList(organizationDataTable, units, (int) OrganizationType.Unit, value);
						break;
						
					case (int)OrganizationType.Unit:				
						/* Determine and select the parent department of the selected unit, and from
						 * there, determine and select the parent institution of that department */
						int unitParentOrgID = (int)organizationDataTable.Select(string.Format("Id = {0}", value))[0]["ParentId"];
						deptParentOrgID = (int)organizationDataTable.Select(string.Format("Id = {0}", unitParentOrgID))[0]["ParentId"];
                        
                        LoadDropDownList(organizationDataTable, institutions, (int)OrganizationType.Institution, RootOrgID);
                        institutions.SelectedValue = deptParentOrgID.ToString();

                        LoadDropDownList(organizationDataTable, departments, (int)OrganizationType.Department, deptParentOrgID);
						departments.SelectedValue = unitParentOrgID.ToString();

						LoadDropDownList(organizationDataTable, units, (int)OrganizationType.Unit, unitParentOrgID);
						units.SelectedValue = value.ToString();

						break;

					default:
						throw new Exception("Unsupported Organization ID.");
				}
			}
		}

        /// <summary>
        /// Used to "gray out" dropdowns without clearing current selection if set to true.
        /// Get returns current IsGrayOutMode status.
        /// </summary>
        public bool IsGrayOutMode
        {
            get
            {
                return isGrayOutMode;
            }
            set
            {
                EnsureChildControls();
                if (value)
                {
                    isGrayOutMode = true;
                    domains.Enabled = institutions.Enabled = units.Enabled = departments.Enabled = false;
                }
                else
                {
                    isGrayOutMode = false;
                    int selectedOrg = SelectedOrganizationID;
                    Reset();
                    SelectedOrganizationID = selectedOrg;
                }
            }
        }

		/// <summary>
		/// Gets the DataTable of all organizations the logged in user
		/// has access to by virtue of being a sys admin or keygiver. 
		/// It first checks for it in session, if not available it retrieves it from the database.
		/// It should be noted that KeyGiver organizations are part of UserContext so there is
		/// no need to cache them here.
		/// </summary>
		private DataTable OrganizationDataTable 
		{
			get 
			{
				DataTable orgDataTable = cachePolicy.GetCachedObject(OrganizationsKey) as DataTable;
				if (orgDataTable == null) 
				{
					UserContext userContext = (UserContext)cachePolicy.GetCachedObject(UserContext.CacheKey);
					if (UserIsAuthorized(userContext)) // sys admin 
					{ 
						orgDataTable = Utility.DataAccess.GetDataSet(Utility.DataAccess.ConnectionString,
							GetOrganizationListSprocName).Tables[0];
						cachePolicy.Cache(OrganizationsKey, orgDataTable);
					}
					else if (userContext.IsKeyGiver)
						orgDataTable = userContext.KeyGiverOrganizations;
				}
				return orgDataTable;
			}
		}
		#endregion

		/// <summary>
		/// Determines whether the logged-in user is a System Administrator
		/// </summary>
		/// <param name="userContext">context of the logged-in user</param>
		/// <returns>true if the user is a SysAdmin, false otherwise</returns>
		protected virtual bool UserIsAuthorized(UserContext userContext)
		{
			return (userContext.ModuleRoleBitmask & (int)Enums.ModuleRoleBit.SystemAdministrator) != 0;
		}

		#region Overrides
		/// <summary>
		/// Create all controls that will be on the Organization selectors. Notice that the creation is more than just instantiating
		/// the controls. It also involves in generating the underlying HTML so that the controls show up per the need.
		/// </summary>
		protected override void CreateChildControls()
		{
			CreateControls();
			GenerateControlTree();
		}

		/// <summary>
		/// Invoked when an event is raised by a child control.
		/// </summary>
		/// <param name="source">Reference to the child control that raised the event</param>
		/// <param name="e">Argument associated with the event</param>
		/// <returns>True if the event was handled by this method; false otherwise</returns>
		protected override bool OnBubbleEvent(object source, EventArgs e)
		{
			// Events raised only by dropdown lists are handled.
			bool handled = false;
            DropDownList organizations = source as DropDownList;
			if (organizations != null)
			{
				// determine exactly what dropdown list control raised the event and 
				// reload the child organizations accordingly
				switch (organizations.ID) 
				{
					case DomainDropDownID:

						institutions.Items.Clear();							
						departments.Items.Clear();
						units.Items.Clear();

						if (organizations.SelectedValue == Helper.SelectPrompt) 
							departments.Enabled = false;

						else
							LoadDropDownList(OrganizationDataTable, institutions, (int)OrganizationType.Institution, Convert.ToInt32(organizations.SelectedValue));
						
						institutions.Enabled = units.Enabled = false;

					break;

					case InstitutionDropDownID:

						departments.Items.Clear();
						units.Items.Clear();

						if (organizations.SelectedValue == Helper.SelectPrompt) 
							departments.Enabled = false;
						else 
							LoadDropDownList(OrganizationDataTable, departments, (int)OrganizationType.Department, Convert.ToInt32(organizations.SelectedValue));						

						units.Enabled = false;

						break;

					case DepartmentDropDownID:

						units.Items.Clear();

						if (organizations.SelectedValue == Helper.SelectPrompt) 
							units.Enabled = false;
						else
							LoadDropDownList(OrganizationDataTable, units, (int)OrganizationType.Unit, Convert.ToInt32(organizations.SelectedValue));

						break;

					default:
						break;
				}
				handled = true;
			}
			return handled;
		}

		/// <summary>
		/// Loads all dropdowns with appropriate organizations. It only
		/// loads if the controls have not been loaded. Not that the property
		/// SelectedOrganizationID also loads the dropdowns.
		/// </summary>
		/// <param name="e">Argument associated with the event</param>
		protected override void OnLoad(EventArgs e)
		{
			base.OnLoad(e);

			if (!Page.IsPostBack) 
			{
				EnsureChildControls();
				if (domains.Items.Count == 0) // controls not loaded
				{
					DataTable organizationDataTable = OrganizationDataTable;	
					LoadDropDownList(organizationDataTable, domains, (int)OrganizationType.Domain, RootOrgID);
					institutions.Items.Add(new ListItem(Helper.SelectPrompt, Helper.SelectPrompt));
				}
			}

			if (SelectedOrganizationID == 0)
				SelectedOrganizationID = RootOrgID;

			domains.Enabled = false;
		}

		/// <summary>
		/// Sets the cache policy
		/// </summary>
		/// <param name="e"></param>
		protected override void OnInit(EventArgs e)
		{
			base.OnInit (e);
			cachePolicy = (Policy.ICachePolicy)Page.Session[Policy.Names.CachePolicyKey];

		}

		/// <summary>
		/// Resets selections back to state of nothing selected
		/// </summary>
		public void Reset()
		{
			EnsureChildControls();
			LoadDropDownList(OrganizationDataTable, domains, (int)OrganizationType.Domain, RootOrgID);

			institutions.Items.Clear();
			institutions.Items.Add(new ListItem(Helper.SelectPrompt, Helper.SelectPrompt));
			institutions.Enabled = false;

			departments.Items.Clear();
			departments.Items.Add(new ListItem(Helper.SelectPrompt, Helper.SelectPrompt));
			departments.Enabled = false;

			units.Items.Clear();
			units.Items.Add(new ListItem(Helper.SelectPrompt, Helper.SelectPrompt));
			units.Enabled = false;

			SelectedOrganizationID = RootOrgID;
		}

		#endregion

		#region Private Methods
		/// <summary>
		/// Create all controls in the Header.
		/// </summary>
		private void CreateControls()
		{
			Controls.Clear();

			domains = new ExtendedDropDownList();
			domains.ID = DomainDropDownID;
			domains.AutoPostBack = true;
			domains.Visible = false;
			domains.CssClass = "DataInput";

			institutions = new ExtendedDropDownList();
			institutions.ID = InstitutionDropDownID;
			institutions.AutoPostBack = true;
			institutions.Enabled = false;
			institutions.CssClass = "DataInput";

			departments = new ExtendedDropDownList();
			departments.ID = DepartmentDropDownID;
			departments.AutoPostBack = true;
			departments.Enabled = false;
			departments.CssClass = "DataInput";

			units = new ExtendedDropDownList();
			units.ID = UnitDropDownID;
			units.AutoPostBack = false;
			units.Enabled = false;
			units.CssClass = "DataInput";
		}

		/// <summary>
		/// Setup the control tree to reflect the actual (and exact) layout of the controls within the Header.
		/// </summary>
		private void GenerateControlTree()
		{
			Table table = new Table();
			Controls.Add(table);

			TableRow row = new TableRow();
			table.Rows.Add(row);

			TableCell cell = new TableCell();
			row.Cells.Add(cell);
			
			if (domains.Visible)
			{
				cell.Controls.Add(domains);

				row = new TableRow();
				table.Rows.Add(row);

				cell = new TableCell();
				row.Cells.Add(cell);
			}

			cell.Controls.Add(new LiteralControl("<nobr>Institution:&nbsp;</nobr>"));
			cell.Attributes["align"] = "right";

			cell = new TableCell();
			row.Cells.Add(cell);
			cell.Controls.Add(institutions);

			row = new TableRow();
			table.Rows.Add(row);

			cell = new TableCell();
			row.Cells.Add(cell);
			cell.Controls.Add(new LiteralControl("<nobr>Department:&nbsp;</nobr>"));
			cell.Attributes["align"] = "right";

			cell = new TableCell();
			row.Cells.Add(cell);
			cell.Controls.Add(departments);

			row = new TableRow();
			table.Rows.Add(row);

			cell = new TableCell();
			row.Cells.Add(cell);
			cell.Controls.Add(new LiteralControl("<nobr>Unit:&nbsp;</nobr>"));
			cell.Attributes["align"] = "right";

			cell = new TableCell();
			row.Cells.Add(cell);
			cell.Controls.Add(units);
		}

		/// <summary>
		/// If not already loaded, loads a dropdown list with organization data. It also adds the 
		/// "--Select--" default list item.
		/// </summary>
		/// <param name="organizationDataTable">The DataTable to be used as a datasource.</param>
		/// <param name="dropDownList">The dropdown to load.</param>
		/// <param name="orgTypeID">The type of organization to be loaded.</param>
		/// <param name="parentOrgID">The ParentID of the organization being loaded.</param>
		private void LoadDropDownList(DataTable organizationDataTable, DropDownList dropDownList, int orgTypeID, int parentOrgID) 
		{
			// don't load the dropdownlist if it's already loaded
            // as long as IsGrayOutMode is not in effect
			if (dropDownList.Items.Count == 0 || !isGrayOutMode)
			{
				dropDownList.DataTextField = "OrganizationName";
				dropDownList.DataValueField = "Id";

				DataView view = organizationDataTable.DefaultView;
				view.RowFilter = string.Format("TypeId = {0} AND ParentId = {1}", orgTypeID, parentOrgID);

				dropDownList.Enabled = true;
				dropDownList.DataSource = view;
				dropDownList.DataBind();

				if (dropDownList.Items.Count > 0)
				{
					ListItem item = new ListItem(Helper.SelectPrompt, Helper.SelectPrompt);
					dropDownList.Items.Insert(0, item);
					item.Selected = true;
				}
				else dropDownList.Enabled = false;
			}
		}

		#endregion

		#region Internal Methods
		/// <summary>
		/// This method is ONLY invoked by the associated designer to show the control contents at design time.
		/// </summary>
		internal void Design()
		{
			EnsureChildControls(); 
		}
		#endregion

		/// <summary>
		/// The extended dropdown list class that forces the event bubbling 
		/// on selected index changed.
		/// </summary>
		private class ExtendedDropDownList : DropDownList 
		{
			/// <summary>
			/// Performs SelectedIndexChanged event wirepup.
			/// </summary>
			/// <param name="e">Argument associated with the event</param>
			protected override void OnInit(EventArgs e)
			{
				base.OnInit (e);
				SelectedIndexChanged += new EventHandler(OnDomainsChange);
			}

			/// <summary>
			/// Raises a bubble event on SelectedIndex change.
			/// </summary>
			/// <param name="sender">Reference to the child control that raised the event</param>
			/// <param name="e">Argument associated with the event</param>
			private void OnDomainsChange(object sender, EventArgs e)
			{
				RaiseBubbleEvent(sender, e);
			}
		}		
	}
	#endregion

	#region Class OrganizationSelectorsDesigner
	/// <summary>
	/// Designer associated with the OrganizationSelectors control.
	/// </summary>
	/// <remarks>
	/// This class cannot be used directly.
	/// </remarks>
	public class OrganizationSelectorsDesigner : ControlDesigner
	{
		#region Overrides
		/// <summary>
		/// Force the OrganizationSelectors control to create its child controls for display at design time.
		/// </summary>
		/// <returns>HTML representation of the OrganizationSelectors control</returns>
		public override string GetDesignTimeHtml()
		{
			((OrganizationSelectors)Component).Design();
			return base.GetDesignTimeHtml();
		} 
		#endregion
	}
	#endregion

	#region Organization Type Enumeration
	/// <summary>
	/// Organization types within insight.
	/// </summary>
	public enum OrganizationType 
	{
		Domain = 1, 
		Institution, 
		Department, 
		Unit
	}
	#endregion
}
